package fr.gdi.android.news.preference.font;

/*
 * Copyright (C) 2011 George Yunaev @ Ulduzsoft
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import android.text.TextUtils;

public class FontManager
{
    
    private static final String[] SYSTEM_FONT_DIRS = new String[] { "/system/fonts", "/system/font", "/data/fonts" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

    private TTFAnalyzer analyzer = new TTFAnalyzer();

    public HashMap<String, String> getFonts(String folder)
    {
        return getFonts(folder, true);
    }
            
    public String getFont(String path)
    {
        if ( TextUtils.isEmpty(path) || !new File(path).isFile() ) return null;
        return analyzer.getTtfFontName(path);   
    }
    
    public HashMap<String, String> getFonts(String folder, boolean includeSystemFonts)
    {
        List<String> dirs = new ArrayList<String>(Arrays.asList(SYSTEM_FONT_DIRS));
        if ( !TextUtils.isEmpty(folder) ) dirs.add(folder);
        return getFonts(dirs.toArray(new String[dirs.size()]));
    }
    
    public HashMap<String, String> getSystemFonts()
    {
        return getFonts(SYSTEM_FONT_DIRS);
    }
    
    public HashMap<String, String> getFonts(String[] fontdirs)
    {
        HashMap<String, String> fonts = new HashMap<String, String>();
        
        for (String fontdir : fontdirs)
        {
            File dir = new File(fontdir);
            
            if (!dir.exists())
            {
                continue;
            }
            
            File[] files = dir.listFiles();
            
            if (files == null) continue;
            
            for (File file : files)
            {
                String fontname = analyzer.getTtfFontName(file.getAbsolutePath());
                
                if (fontname != null)
                {
                    fonts.put(file.getAbsolutePath(), fontname);
                }
            }
        }
        
        return fonts.isEmpty() ? null : fonts;
    }
}

// The class which loads the TTF file, parses it and returns the TTF font name
class TTFAnalyzer
{
    // This function parses the TTF file and returns the font name specified in
    // the file
    public String getTtfFontName(String fontFilename)
    {
        try
        {
            // Parses the TTF file format.
            // See http://developer.apple.com/fonts/ttrefman/rm06/Chap6.html
            m_file = new RandomAccessFile(fontFilename, "r"); //$NON-NLS-1$
            
            // Read the version first
            int version = readDword();
            
            // The version must be either 'true' (0x74727565) or 0x00010000
            if (version != 0x74727565 && version != 0x00010000) return null;
            
            // The TTF file consist of several sections called "tables", and we
            // need to know how many of them are there.
            int numTables = readWord();
            
            // Skip the rest in the header
            readWord(); // skip searchRange
            readWord(); // skip entrySelector
            readWord(); // skip rangeShift
            
            // Now we can read the tables
            for (int i = 0; i < numTables; i++)
            {
                // Read the table entry
                int tag = readDword();
                readDword(); // skip checksum
                int offset = readDword();
                int length = readDword();
                
                // Now here' the trick. 'name' field actually contains the
                // textual string name.
                // So the 'name' string in characters equals to 0x6E616D65
                if (tag == 0x6E616D65)
                {
                    // Here's the name section. Read it completely into the
                    // allocated buffer
                    byte[] table = new byte[length];
                    
                    m_file.seek(offset);
                    read(table);
                    
                    // This is also a table. See
                    // http://developer.apple.com/fonts/ttrefman/rm06/Chap6name.html
                    // According to Table 36, the total number of table records
                    // is stored in the second word, at the offset 2.
                    // Getting the count and string offset - remembering it's
                    // big endian.
                    int count = getWord(table, 2);
                    int string_offset = getWord(table, 4);
                    
                    // Record starts from offset 6
                    for (int record = 0; record < count; record++)
                    {
                        // Table 37 tells us that each record is 6 words -> 12
                        // bytes, and that the nameID is 4th word so its offset
                        // is 6.
                        // We also need to account for the first 6 bytes of the
                        // header above (Table 36), so...
                        int nameid_offset = record * 12 + 6;
                        int platformID = getWord(table, nameid_offset);
                        int nameid_value = getWord(table, nameid_offset + 6);
                        
                        // Table 42 lists the valid name Identifiers. We're
                        // interested in 4 but not in Unicode encoding (for
                        // simplicity).
                        // The encoding is stored as PlatformID and we're
                        // interested in Mac encoding
                        if (nameid_value == 4 && platformID == 1)
                        {
                            // We need the string offset and length, which are
                            // the word 6 and 5 respectively
                            int name_length = getWord(table, nameid_offset + 8);
                            int name_offset = getWord(table, nameid_offset + 10);
                            
                            // The real name string offset is calculated by
                            // adding the string_offset
                            name_offset = name_offset + string_offset;
                            
                            // Make sure it is inside the array
                            if (name_offset >= 0 && name_offset + name_length < table.length) return new String(table, name_offset, name_length);
                        }
                    }
                }
            }
            
            return null;
        }
        catch (FileNotFoundException e)
        {
            // Permissions?
            return null;
        }
        catch (IOException e)
        {
            // Most likely a corrupted font file
            return null;
        }
    }
    
    // Font file; must be seekable
    private RandomAccessFile m_file = null;
    
    // Helper I/O functions
    private int readByte() throws IOException
    {
        return m_file.read() & 0xFF;
    }
    
    private int readWord() throws IOException
    {
        int b1 = readByte();
        int b2 = readByte();
        
        return b1 << 8 | b2;
    }
    
    private int readDword() throws IOException
    {
        int b1 = readByte();
        int b2 = readByte();
        int b3 = readByte();
        int b4 = readByte();
        
        return b1 << 24 | b2 << 16 | b3 << 8 | b4;
    }
    
    private void read(byte[] array) throws IOException
    {
        if (m_file.read(array) != array.length) throw new IOException();
    }
    
    // Helper
    private int getWord(byte[] array, int offset)
    {
        int b1 = array[offset] & 0xFF;
        int b2 = array[offset + 1] & 0xFF;
        
        return b1 << 8 | b2;
    }
}